﻿/**
 * Licensed under the MIT License
 *
 * Copyright (c) 2010 Alexandre Croiseaux
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
package marcel.display
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.display.Loader;
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.geom.Matrix;
	import flash.geom.Rectangle;
	import marcel.debug.*;
	import marcel.utils.Aligner;
	import marcel.utils.DisplayObjectUtils;
	import marcel.utils.IDestroyable;
	
	/**
	 * Extended MovieClip class
	 * @author Alexandre Croiseaux
	 */
	dynamic public class View extends MovieClip implements IDestroyable
	{
		//--------------------------------------------------------------------------
		//
		//  Private vars
		//
		//--------------------------------------------------------------------------
		private var _removedChildrenToBitmap:/*DisplayObject*/Array;
		private var _storedBitmap:Bitmap;
		private var _orientation:String;
		private var _gap:Number = 5;
		
		//--------------------------------------------------------------------------
		//
		//  Public vars
		//
		//--------------------------------------------------------------------------
		public static const ORIENTATION_HORIZONTAL:String = "horizontal";
		public static const ORIENTATION_VERTICAL:String = "vertical";
		
		
		//--------------------------------------------------------------------------
		//
		//  Constructor
		//
		//--------------------------------------------------------------------------
		/**
		 * Constructs a new View instance
		 * @param	x	The x position of the view, in pixels
		 * @param	y	The y position of the view, in pixels
		 * @param	orientation	Specifies the layout orientation of the current view. Use a constant of the View Class. Children are automatically position vertically or horizontally.
		 * @param	gap	Specifies the gap in pixel used between each child display object, if the orientation value is set.
		 */
		public function View(x:Number = 0, y:Number = 0, orientation:String = null, gap:Number = 5)
		{
			this.x = x;
			this.y = y;
			_orientation = orientation;
			_gap = gap;
		}
		
		//--------------------------------------------------------------------------
		//
		//  Core methods
		//
		//--------------------------------------------------------------------------
		/**
		 * Set the width &amp; height properties
		 * @param	w	set the width to passed value
		 * @param	h	set the height to passed value
		 */
		public function setSize(w:uint, h:uint):void
		{
			width = w;
			height = h;
		}
		
		/**
		 * Set the x &amp; y properties
		 * @param	x	set the x to passed value
		 * @param	x	set the x to passed value
		 */
		public function setPos(x:int, y:int):void
		{
			this.x = x;
			this.y = y;
		}
		
		/**
		 * Set both scaleX &amp; scaleY properties (range [0-1])
		 * @param	scale	set the scaleX &amp; scaleY to passed value
		 */
		public function setScale(scale:Number):void
		{
			this.scaleX = scale;
			this.scaleY = scale;
		}
		
		/**
		 * Adds a DisplayObject to the current View display list.
		 * @param	child		the DisplayObject to add
		 * @return	the added DisplayObject instance.
		 */
		override public function addChild(child:DisplayObject):DisplayObject
		{
			if (_orientation) position(child);
			return super.addChild(child);
		}
		
		/**
		 * Adds a DisplayObject to the current View display list, and applies the initProps to the DisplayObject.
		 * @param	child		the DisplayObject to add
		 * @param	initProps	An object with key/values which will be applied the specified child
		 * @return	the added DisplayObject instance.
		 */
		public function addChildWithProps(child:DisplayObject, initProps:Object = null):DisplayObject
		{
			if (_orientation) position(child);
			if (initProps)
			{
				for (var prop:String in initProps) child[prop] = initProps[prop];
			}
			return super.addChild(child);
		}
		
		/**
		 * Returns an Array containing all the DisplayObject children of the current container.
		 */
		public function getChildren():/*DisplayObject*/Array
		{
			var children:Array = new Array();
			for (var i:int = 0; i < numChildren; i++)
			{
				children.push(getChildAt(i));
			}
			return children;
		}
		
		/**
		 * Specifies the layout orientation of the current view. Use a constant of the View Class. Children are automatically position vertically or horizontally.
		 */
		public function get orientation():String { return _orientation; }
		public function set orientation(value:String):void
		{
			_orientation = value;
			refresh();
		}
		
		/**
		 * Specifies the gap in pixel used between each child display object, if the orientation value is set.
		 */
		public function get gap():Number { return _gap; }
		public function set gap(value:Number):void
		{
			_gap = value;
			refresh();
		}
		
		/**
		 * Transforms the current View into a bitmap, by drawing all content into a bitmapdata.
		 * @param	smoothed	Whether or not the bitmap is smoothed when scaled.
		 * @param 	pixelSnapping	Whether or not the Bitmap object is snapped to the nearest pixel.
		 * @param	transparent	catch DisplayObject transparency or not
		 * @param	fillColor	if transparent is false, replace transparent values by this color
		 * @param	registerPointCorrection	indicates if a matrix must be used to correct the source register point.
		 * 					Set true if the source contains display with negative coords.
		 * @return	the created bitmap instance.
		 */
		public function transformToBitmap(smoothed:Boolean = true, pixelSnapping:String = "auto"/*PixelSnapping.AUTO*/, transparent:Boolean = false, fillColor:uint = 0x00FFFFFF, registerPointCorrection:Boolean = true):Bitmap
		{
			// snapshot
			var bounds:Rectangle = getBounds(this);
			var mat:Matrix = new Matrix();
			if (registerPointCorrection) mat.translate(-bounds.left, -bounds.top);
			var bmpData:BitmapData = new BitmapData(Math.round(bounds.width), Math.round(bounds.height), transparent, fillColor);
			bmpData.draw(this, mat);
			
			_storedBitmap = new Bitmap(bmpData, pixelSnapping, smoothed);
			if (registerPointCorrection)
			{
				_storedBitmap.x = getBounds(this).left;
				_storedBitmap.y = getBounds(this).top;
			}
			
			// remove children but keep sensor
			_removedChildrenToBitmap = [];
			var hit:Sprite;
			while (numChildren > 0)
			{
				var child:DisplayObject = removeChildAt(0);
				_removedChildrenToBitmap.push(child);
				if (hitArea == child) hit = child as Sprite;
			}
			if (hit) addChild(hit);
			
			// add bmp
			addChild(_storedBitmap);
			return _storedBitmap;
		}
		
		/**
		 * Updates the bitmap that has been created using the transformToBitmap() method by redrawing the content.
		 * @param	fillColor	if created bitmap is not transparent, replace transparent values by this color
		 * @return	the updated bitmap instance.
		 */
		public function updateBitmapTransformation(fillColor:uint = 0x00FFFFFF):Bitmap
		{
			if (_storedBitmap == null) throw "[View.updateTransformedBitmap()] No bitmap created ! Please call transformToBitmap() first.";
			
			// remove bitmap & hitarea
			while (numChildren > 0) removeChildAt(0);
			
			// re-add removed items
			for (var i:uint = 0; i < _removedChildrenToBitmap.length; i++) addChild(_removedChildrenToBitmap[i]);
			
			// redraw
			return transformToBitmap(_storedBitmap.smoothing, _storedBitmap.pixelSnapping, _storedBitmap.bitmapData.transparent, fillColor);
		}
		
		/**
		 * Removes the bitmap that has been created using the transformToBitmap() method, and display removed children.
		 */
		public function removeBitmapTransformation():void
		{
			if (_storedBitmap != null)
			{
				while (numChildren > 0) removeChildAt(0);
				_storedBitmap = null;
				for (var i:uint = 0; i < _removedChildrenToBitmap.length; i++) addChild(_removedChildrenToBitmap[i]);
				_removedChildrenToBitmap = [];
			}
		}
		
		/**
		 * Prepares the current View to an unload process; this method is empty and MUST be overriden to stop all children processes, allowing UI swf to be unloaded.
		 * This method is automatically called by the the setClosed() method if autoDestroy propertiy is true (default).
		 * This method must deactivate the following tasks :
		 * 	Tell any loaded .swf child assets to disable themselves.
		 * 	Stop any sounds from playing.
		 * 	Stop the main timeline, if it is currently playing.
		 * 	Stop any movie clips that are currently playing.
		 * 	Close any connected network objects, such as instances of Loader, URLLoader, Socket, XMLSocket, LocalConnection, NetConnections, and NetStream.
		 * 	Release all references to cameras and microphones.
		 * 	Unregister all event listeners in the .swf (particularly Event.ENTER_FRAME, and mouse and keyboard listeners)
		 * 	Stop any currently running intervals (via clearInterval()).
		 * 	Stop any Timer objects (via the Timer class’s instance method stop()).
		 */
		public function destroy():void
		{
			var children:Array = DisplayObjectUtils.getChildren(this, true);
			var len:uint = children.length;
			for (var i:uint = 0; i < len; i++)
			{
				var d:DisplayObject = children[i] as DisplayObject;
				if (d != null)
				{
					try
					{
						d.filters = null;
						Tween.removeTweens(d, true);
						if (d is IDestroyable) (d as IDestroyable).destroy();
						if (d is MovieClip) (d as MovieClip).stop();
						if (d is Loader)
						{
							(d as Loader).unload();
							try { d["unloadAndStop"](); } // flash player >= 10
							catch (e:Error) { };
						}
						if (totalFrames > 1) super.stop();
						if (d.parent)
						{
							if (d.parent is Loader) (d.parent as Loader).unload();
							else d.parent.removeChild(d);
						}
					}
					catch (e:Error)
					{
						debuge(this, "Error while destroying view, see stacktrace:\n" + e.getStackTrace());
					}
				}
			}
		}
		
		//--------------------------------------------------------------------------
		//
		//  Private methods
		//
		//--------------------------------------------------------------------------
		private function position(child:DisplayObject):void
		{
			var prevChild:DisplayObject = numChildren > 0 ? getChildAt(numChildren - 1) : null;
			if (prevChild)
			{
				if (_orientation == ORIENTATION_HORIZONTAL) child.x = prevChild.x + prevChild.getBounds(prevChild).width + gap;
				else if (_orientation == ORIENTATION_VERTICAL) child.y = prevChild.y + prevChild.getBounds(prevChild).height + gap;
				else child.x = child.y = 0;
			}
		}
		
		private function refresh():void
		{
			var a:Array = [];
			for (var i:int = 0; i < numChildren; i++) a.push(getChildAt(i));
			if (_orientation == ORIENTATION_HORIZONTAL) Aligner.cascadeX(a, 0, gap);
			else if (_orientation == ORIENTATION_VERTICAL) Aligner.cascadeY(a, 0, gap);
			else
			{
				Aligner.alignX(a, 0);
				Aligner.alignY(a, 0);
			}
		}
	}
}